<?php
/**
 * Allowance class.
 *
 * 
 * 
 * The Allowance Class is responsible for the CRUD of the time allotments for a give subscription.
 * There are two types of allowances, an ADD and a PAUSE. 
 * 
 * 
 * 
 * PHP version 5
 *
 * LICENSE: This source file is subject to version 3.01 of the PHP license
 * that is available through the world-wide-web at the following URI:
 * http://www.php.net/license/3_01.txt.  If you did not receive a copy of
 * the PHP License and are unable to obtain it through the web, please
 * send a note to license@php.net so we can mail you a copy immediately.
 *
 * @package    Subbear Subscription Management
 * @author     Shawn Wernig ( shawn@eggplantstudios.ca )
 * @copyright  2013 
 * @license    http://www.eggplantstudios.ca
 * @version    2.0
 */



class Allowance {

    public $subscription_id;
    public $allowance_id; // array of 1s and 0s.
    
    public $datestamp;
           
    public $start_date;
    public $start_week;
    
    public $queue;
    
    public $current_duration;
    public $total_duration;
    
    public $type = 'add'; // (add/pause)
    public $sticky = false; //sticky types cant be user-removed, and maintain their original position.

    private static $table_name = "_allowance";
    
    public function __construct() {}
    
    
    /* --------------------------------------- GETS --------------------------------------- */
    
    
    /**
     * Gets the full table name for this publication
     *
     * @return string
     * @author epstudios
     */
    public static function get_tablename() {
        global $TABLE_PREFIX;
        return $TABLE_PREFIX . self::$table_name; 
    }
    
    
    /**
     * Gets an attribute
     *
     * @return void
     * @param $object = the object to display; Typically a class, or array.
     * @author epstudios
     */   
    public function attr($attr) {
        echo ucwords( htmlspecialchars($this->$attr) );
    }
    public function get_attr($attr) {
        return  $this->$attr;
    }
    
    
     
    /**
     * If this instance has been pulled from the database, or updated, there will be an ID. 
     * If created from a POST for example, there will be no id.
     *
     * @return void
     * @param bool 
     * @author epstudios
     */ 
    public function is_new(){
        return (!isset($this->allowance_id) || $this->allowance_id == 0) ? true : false ;
    }
    
    
    /**
     * Translates the type attribute into a 1 = add, 2 = pause
     *
     * @return void
     * @param $object = the object to display; Typically a class, or array.
     * @author epstudios
     */ 
    function get_type_code(){
        switch ( $this->type ) {
            case 'add':
                return 1;
            break;
            case 'pause':
                return 2;
            break;
        }
    }  

    /* --------------------------------------- CRUDS --------------------------------------- */
    
    /**
     * Creates, validates and adds an allowance to an existing subscription.
     * Called when an allowance is saved.
     *
     * @return void
     * @param bool
     * @author epstudios
     */ 
    public static function add_allowance() {
        global $session;    
        $allowance = Allowance::instantiate( $_POST );

        if ( $allowance->save() ) {
            return true; 
        } else {
            $session->add_message('ERROR: Subscription not updated.');
            return false;
        }
    }
    
    /**
     * Creates, validates and adds an allowance to an existing subscription.
     * Called when an allowance is saved.
     *
     * @return void
     * @param bool
     * @author epstudios
     */ 
    public static function adjust_allowance() {
        global $session;    
        $adjustment  = $_POST['single_allowance_adjustment_amount'];
        if ( $adjustment > 1780 ) {
            $adjustment = 1780; // do not allow ridiculaous inputs.
            $session->add_message('NOTICE: You are trying to add more than 5 years worth of time, the maximum is 5 years.');
        }
        $allowance = self::find_by_id($_POST['allowance_id']);
        
        if ( $allowance->total_duration + $adjustment > 0 ) {
            $sql = "
            UPDATE ".self::get_tablename()."
            SET total_duration = total_duration+$adjustment WHERE allowance_id=".$_POST['allowance_id']."
            ";
            if ( self::update_by_sql($sql) ) {
                $session->add_message("SUCCESS: Allowance id ".$_POST['allowance_id']." has been successfully adjusted.");
            }
        } else {
            self::delete($allowance->allowance_id);
        } 
        Subscription::refresh_status($_POST['subscription_id']);
    }
   
    /**
     * Validates and re-saves all allowance to the database, including the new allowance.
     *
     * @return void
     * @param bool
     * @author epstudios
     */         
    public function save(){
     global $session;   
     $new_allowances = $this->_validate();
        if ( $new_allowances ) {
            
            if ( $this->_update_allowances( $new_allowances, $new_allowances[0]->subscription_id )  ){
                $session->add_message("SUCCESS: Allowance has been successfully saved.");
                Subscription::refresh_status($this->subscription_id);
                return true;
            }
            
            $session->add_message("ERROR: Allowance could not be successfully saved.");
            return false;
            
        } 
    }
    
    
    
    
    /**
     * A complicated function.
     * 1. Validates the new allowance.
     * 2. If its a pause -> validates this new allowance as a pause.
     * 3. If its an add -> validates this new allowance as an add.
     * 4. Grabs all allowances and resolves the new allowances placement.
     * 5. Returns all allowances for re-insertion.
     *
     * @return array = array of allowances
     * @author epstudios
     */ 

    private function _validate(){
        global $session;
        $validator = new Validator;
        
        /**
         * 
         * 
         * Validate this allowance.
         */
        $sub                = Subscription::find_by_id( $this->subscription_id );
        $current_week       = get_weeknum() - $sub->start_week;
        $current_allowances = Allowance::find_by_subscription($this->subscription_id);
        $this->start_date   = (isset($_POST['start_date'])) ? $_POST['start_date'] : '-';
                       
   
        // resolve duration
        if ( ( !isset($this->total_duration) || empty($this->total_duration) ) && isset($_POST['total_duration_other']) ) {
             $this->total_duration = $_POST['total_duration_other'];
        }
        
        if ( $this->total_duration > 1780 ) {
            $this->total_duration = 1780; // do not allow ridiculaous inputs.
            $session->add_message('NOTICE: You are trying to add more than 5 years worth of time, the maximum is 5 years.');
        }
        
        
        
        // Some type specific handlers
        // Converts REMOVE and SET types to ADDs.
        switch( $this->type ) {
            case 'pause': break;
            case 'add': break;
            case 'remove':
                $this->type = 'add'; $this->total_duration = 0 - $this->total_duration;
                break;
            case 'set':
                if ( isset($_POST['end_date']) ) {
                    // Get week difference from the total duration, do the necessary addition/sub
                    
                    $offset =  get_weeknum( strtotime($_POST['end_date'] ) ) -  get_weeknum( strtotime($sub->get_end_date()) );
                    if ($offset != 0 ) {
                        $this->type             = 'add';
                        $this->total_duration   = $offset;
                    } else {
                        $session->add_message('ERROR: The end date is already this date!');
                    }
                }
                break;
        }
        
        /**
         * 
         * 
         * Here is where we resolve this allowance with the others.
         * 
         */
        if ($this->type == 'pause') {
            /**
             * 
             *  Pause logic:
             * 
             */
            
            $total_duration = 0;
            foreach ( $current_allowances as $allowance ) {
                if ( $allowance->type != 'expiring' ) {
                    $total_duration += $allowance->total_duration;
                }    
            }
            
            // check for pause splits
            $pause_start_week   = get_weeknum(strtotime($this->start_date));
            $pause_week         = $pause_start_week - get_weeknum();

            if ($pause_week >= 0 && $pause_week <= ($total_duration - $current_week) ) {
                // The pause is in the future, and before the expiry.
                // Do split.

                // Which add do we split?
                $target_week = $pause_week + $current_week;
                
                
                    $counter = 0;
                    $bank = $target_week;
                    foreach ( $current_allowances as $allowance ) {
                        // is $target_week within this duration?
                        if ($bank <= $allowance->total_duration){
                            break;
                        } else {
                            $bank -= $allowance->total_duration; 
                        }
                        $counter ++;
                    }
                // counter indicates is our add we need to split.
                if ( $current_allowances[$counter]->type == "add") {
                      
                     // The duration of this allowance, we will use it to split.
                     $duration =  $current_allowances[$counter]->total_duration;
                     
                     // New total duration, this is the split. we use $bank, it has the correct amt.
                     $current_allowances[$counter]->total_duration = $bank;

                     $duration -= $bank;
                     
                     // $this is our pause allowance
                     $new_allowance = Allowance::instantiate(array(
                                'type' => 'add',
                                'subscription_id' => $this->subscription_id,
                                'total_duration' => $duration,
                                'start_date' => '',
                     ));                     
                     
                    //Re-order and send off.
                    $i = 0;
                    $new_allowances = array();
                    
                    // $current_allowances[] = $this;
                    // $current_allowances[] = $new_allowance;
                    foreach ( $current_allowances as $allowance ) {
                        $new_allowances[] = $allowance;
                        if ($i == $counter ) {
                            $new_allowances[] = $this;
                            $new_allowances[] = $new_allowance;
                        }
                        $i ++;                     
                    }
                   $counter = 1;
                   
                    foreach ( $new_allowances as $key => $allowance ) {
                        if ( $allowance->total_duration == 0 ) {
                                unset($new_allowances[$key]);
                                continue;
                            }
                        $allowance->queue = $counter;
                        $counter ++;
                    }
                    sort($new_allowances);
                    
                    return $new_allowances;
                    
                } else {
                    // Combine multiple pauses:
                    $current_allowances[$counter]->total_duration += $this->total_duration;
                    return $current_allowances;   
                }
                
            } else {
                $session->add_message("ERROR: Pauses must be within the scope of the current allowance.");
            }
            
            
        } else {
            /**
             * 
             *  Add validation
             * 
             */
            $current_allowances[] = $this;   
            
            $total_duration = 0;
            foreach ( $current_allowances as $allowance ) {
                    $total_duration += $allowance->total_duration;
            } 
            
         
            // Check for cumulative adds
            $counter = 0;
            foreach ( $current_allowances as $allowance ) {
                if($counter > 0 ) {
                   if ($current_allowances[$counter]->type == "add" && $current_allowances[$counter-1]->type == "add") {
                       // Two consecutive ads. Let's comnine shall we?
                       $current_allowances[$counter]->total_duration +=   $current_allowances[$counter-1]->total_duration;        
                       unset($current_allowances[$counter-1]);
                   }   
                }    
                $counter ++;
            }
            
            // DONT ALLOW US TO REMOVE WEEKS THAT THEY CAN'T.
            if ( $current_week > $total_duration ) {
                $session->add_message("ERROR: You cannot remove that many weeks.");
                return false;
            }
            //Re-order and send off.
            $counter = 1;
            foreach ( $current_allowances as $allowance ) {
                $allowance->queue = $counter;
                $counter ++;
            }
            sort($current_allowances);
            
            return $current_allowances;  
    
        }

    }
    

    
    /**
     * Deletes an allowance. Cannot delete the first allowance. Resolves the subscription status if changes.
     * 
     * @return array = array of allowances
     * @author epstudios
     */ 
    public static function delete($allowance_id) {
        global $db;
        global $session;

        $allowance = self::find_by_id($allowance_id);
        if ( $allowance ) {
            $sub = Subscription::find_by_id($allowance->subscription_id);
    
            if ($allowance && $sub) {
               $available_allowances = count($sub->allowances);
               
                   $sql =  "DELETE FROM ".self::get_tablename()." WHERE allowance_id={$allowance_id} LIMIT 1";
                   $result = $db->query( $sql );      
                   if ( $result ) {
                       $sub->refresh_status($sub->subscription_id);
                       $session->add_message("SUCCESS: Allowance #{$allowance_id} has been removed. ");
                   } else {
                       $session->add_message("ERROR: Allowance could not be updated.");
                       return false;
                   }
               if ( $available_allowances == 1 ) {
                   Subscription::_delete( $sub->subscription_id);
               }              
               return true;
            }
        }
        return false;
        
        
    } 

    /**
     * Deletes all allowances for this subscription, and re-writes them.
     * We do this because there may be significant changes to the allowance structure queue.
     *
     * @return array = array of allowances
     * @author epstudios
     */ 


    public static function _update_allowances( $new_allowances, $subscription_id ) {
        global $db;
        global $session;
        
        $sql =  "DELETE FROM ".self::get_tablename()." WHERE subscription_id={$subscription_id}";
        $result = $db->query( $sql );
        
        if ($result) {
            foreach ($new_allowances as $allowance ) {
                
                $sql =  "INSERT INTO ".self::get_tablename()." ";
                $sql .= "(datestamp, subscription_id, queue, total_duration, type) ";
                $sql .= "VALUES ('".date( 'Y-m-d H:i:s', time() )."', '{$subscription_id}', '{$allowance->queue}', '{$allowance->total_duration}', '{$allowance->type}')";
                
                $result = $db->query( $sql );
            }
            
            return ($result) ? TRUE : FALSE;
        }
        return false;
        
    } 



    /* --------------------------------------- SQL --------------------------------------- */
    
    /**
     * Returns an allowance by its ID
     *
     * @return object
     * @author epstudios
     */ 
    public static function find_by_id($allowance_id) {
    $objects = self::find_by_sql("SELECT * FROM " . self::get_tablename() . " WHERE allowance_id={$allowance_id}" );
    return ($objects) ? $objects[0] : false;
    }   
    
    /**
     * Returns an allowance by a subscription id
     *
     * @return array of object(s)
     * @author epstudios
     */ 
    public static function find_by_subscription($subscription_id) {
        return self::find_by_sql("SELECT * FROM " . self::get_tablename() . " WHERE subscription_id=" . $subscription_id . " ORDER BY queue ASC");
    }
    
    /**
     * Returns an allowance by other
     *
     * @return object
     * @author epstudios
     */ 
    public static function find_by_sql ( $sql="") {
        global $db;
        $result_set = $db->query( $sql );
        
        $object_array = array();
        while ($row = $db->fetch_array($result_set)) {
            $object_array[] = self::instantiate($row);
        }
        // RETURNS AN ARRAY OF OBJECTS.
        return $object_array;
    }
    
    /**
     * Updates an allowance by SQL
     *
     * @return object
     * @author epstudios
     */ 
    public static function update_by_sql ( $sql="") {
        global $db;
        $result_set = $db->query( $sql );
        
        if ($db->affected_rows() == 1) return TRUE;
        
        return FALSE;
    }
    
    /**
     * Initializes an instance of this class by an array.
     *
     * @return object
     * @author epstudios
     */ 
    public static function instantiate ($record) {
        //Check the record array for exist and array.
        $object = new self;
        foreach ( $record as $key=>$value ) {
            //  LOOP THROUGH EACH key/value PAIR IN RECORD...
            if ( $object->_has_attribute($key)) {
                //  IF object HAS A MATCHING VARIABLE, SET IT AS value
                $object->$key = $value;
            }
        }
        return $object;
    }

     /**
     * Tests if an allowance has an attribute. Used to initialize.
     *
     * @return bool
     * @author epstudios
     */     
        
    private function _has_attribute ( $key ) {
        //  NOTE: INCLUDES PRIVATE ATTRIBUTES.
        $object_vars = get_object_vars($this);
        
        return array_key_exists($key, $object_vars);
        
    }
    
}
?>